Macro Constraint Library

Currently, Macro includes the following constraints:

Balance constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::BalanceConstraint, v::AbstractVertex, model::Model)

Add a balance constraint to the vertex v.

  • If v is a Node, a demand balance constraint is added.
  • If v is a Transformation, this constraint ensures that the stoichiometric equations linking the input and output flows are correctly balanced.

\[\begin{aligned} \sum_{\substack{i\ \in \ \text{balance\_eqs\_ids(v)}, \\ t\ \in \ \text{time\_interval(v)}} } \text{balance\_eq(v, i, t)} = 0.0 \end{aligned}\]

source

Capacity constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::CapacityConstraint, e::Edge, model::Model)

Add a capacity constraint to the edge e. If the edge is unidirectional, the functional form of the constraint is:

\[\begin{aligned} \text{flow(e, t)} \leq \text{availability(e, t)} \times \text{capacity(e)} \end{aligned}\]

If the edge is bidirectional, the constraint is:

\[\begin{aligned} i \times \text{flow(e, t)} \leq \text{availability(e, t)} \times \text{capacity(e)} \end{aligned}\]

for each time t in time_interval(e) for the edge e and each i in {0, 1}. The function availability returns the time series of the capacity factor of the edge at time t.

source
MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::CapacityConstraint, e::EdgeWithUC, model::Model)

Add a capacity constraint to the edge e with unit commitment. If the edge is unidirectional, the functional form of the constraint is:

\[\begin{aligned} \sum_{t \in \text{time\_interval(e)}} \text{flow(e, t)} \leq \text{availability(e, t)} \times \text{capacity(e)} \times \text{ucommit(e, t)} \end{aligned}\]

If the edge is bidirectional, the constraint is:

\[\begin{aligned} i \times \text{flow(e, t)} \leq \text{availability(e, t)} \times \text{capacity(e)} \times \text{ucommit(e, t)} \end{aligned}\]

for each time t in time_interval(e) for the edge e and each i in [-1, 1]. The function availability returns the time series of the availability of the edge at time t.

source

CO2 capacity constraint

The CO2 capacity constraint is used to limit the amount of CO2 that can be emitted by a single CO2 node.

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::CO2CapConstraint, n::Node{CO2}, model::Model)

Constraint the CO2 emissions of CO2 on a CO2 node n to be less than or equal to the value of the rhs_policy for the CO2CapConstraint constraint type. If the price_unmet_policy is also specified, then a slack variable is added to the constraint to allow for the CO2 emissions to exceed the value of the rhs_policy by the amount specified in the price_unmet_policy for the CO2CapConstraint constraint type. Please check the example case in the ExampleSystems folder of Macro, or the Macro Input Data section of the documentation for more information on how to specify the rhs_policy and price_unmet_policy for the CO2CapConstraint constraint type.

Therefore, the functional form of the constraint is:

\[\begin{aligned} \sum_{t \in \text{time\_interval(n)}} \text{emissions(n, t)} - \text{price\_unmet\_policy(n)} \times \text{slack(n)} \leq \text{rhs\_policy(n)} \end{aligned}\]

"Emissions" in the above equation is the net balance of CO2 flows into and out of the CO2 node n.

Enabling CO2 emissions for an asset

For modelers: To allow for an asset to contribute to the CO2 emissions of a CO2 node, the asset must have an "emissions" key in its balance_data dictionary. The value of this key should be the emission_rate of the asset.

source

Long-duration storage constraints

These additional constraints (and variables) can be used to ensure that storage levels of long-duration storage systems do not exceed installed capacity over non-representative subperiods.

For a complete description of the constraints, see the paper: "Improved formulation for long-duration storage in capacity expansion models using representative periods", Federico Parolin, Paolo Colbertaldo, Ruaridh Macdonald, 2024, https://doi.org/10.48550/arXiv.2409.19079.

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::LongDurationStorageImplicitMinMaxConstraint, g::LongDurationStorage, model::Model)

Adds constraints to ensure that the storage levels of long-duration storage systems do not exceed installed capacity over non-representative subperiods.

The functional form of the two constraints are:

\[\begin{aligned} \text{storage\_balance(p)} + \text{max\_storage\_level(r)} - \text{storage\_level(tstart(p))} &\leq \text{capacity(g)} \\ \text{storage\_balance(p)} + \ \text{min\_storage\_level(r)} - \text{storage\_level(tstart(p))} &\geq 0 \end{aligned}\]

where:

  • p is a non-representative subperiod.
  • r is the representative subperiod used to model p.
  • tstart(p) is the first timestep of the representative subperiod r used to model the non-representative subperiod p.
  • storage_balance(p) is the balance of the storage resource at the non-representative subperiod p and is defined as

\[\begin{aligned} \text{storage\_balance(p)} = (1 - \text{loss\_fraction}) \times \text{storage\_initial(p)} + \frac{\text{flow(discharge\_edge, tstart(p))}}{\text{efficiency(discharge\_edge)}} - \text{efficiency(charge\_edge)} \times \text{flow(charge\_edge, tstart(p))} \end{aligned}\]

  • max_storage_level(r) and min_storage_level(r) are the maximum and minimum storage levels for the representative subperiod r, respectively. These are used to constrain the storage levels as follows:

\[\begin{aligned} \text{min\_storage\_level(t')} \leq \text{storage\_level(t)} \leq \text{max\_storage\_level(t')} \end{aligned}\]

for each time t in the time interval of the storage resource g. t' is the corresponding time in the representative subperiod r used to model the time interval of the storage resource g.

Only applies to long duration energy storage

This constraint only applies to long duration energy storage resources. To model a storage technology as long duration energy storage, the user must set long_duration = true in the Storage component of the asset in the .json file. Check the the file hydropower.json in the ExampleSystems/eastern_us_three_zones folder for an example of how to model a long duration energy storage resource.

source

Maximum capacity constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::MaxCapacityConstraint, y::Union{AbstractEdge,AbstractStorage}, model::Model)

Add a max capacity constraint to the edge or storage y. The functional form of the constraint is:

\[\begin{aligned} \text{capacity(y)} \leq \text{max\_capacity(y)} \end{aligned}\]

source

Maximum non-served demand constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::MaxNonServedDemandConstraint, n::Node, model::Model)

Add a max non-served demand constraint to the node n. The functional form of the constraint is:

\[\begin{aligned} \sum_{s\ \in\ \text{segments\_nsd(n)}} \text{non\_served\_demand(n, s, t)} \leq \text{demand(n, t)} \end{aligned}\]

for each time t in time_interval(n) for the node n.

source

Maximum non-served demand per segment constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(
    ct::MaxNonServedDemandPerSegmentConstraint,
    n::Node,
    model::Model,
)

Add a max non-served demand per segment constraint to the node n. The functional form of the constraint is:

\[\begin{aligned} \text{non\_served\_demand(n, s, t)} \leq \text{max\_non\_served\_demand(n, s)} \times \text{demand(n, t)} \end{aligned}\]

for each segment s in segments_non_served_demand(n) and each time t in time_interval(n) for the node n. The function segments_non_served_demand returns the segments of the non-served demand of the node n as defined in the input data nodes.json.

source

Maximum storage level constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::MaxStorageLevelConstraint, g::AbstractStorage, model::Model)

Add a max storage level constraint to the storage g. The functional form of the constraint is:

\[\begin{aligned} \text{storage\_level(g, t)} \leq \text{max\_storage\_level(g)} \times \text{capacity(g)} \end{aligned}\]

for each time t in time_interval(g) for the storage g.

source

Minimum capacity constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::MinCapacityConstraint, y::Union{AbstractEdge,AbstractStorage}, model::Model)

Add a min capacity constraint to the edge or storage y. The functional form of the constraint is:

\[\begin{aligned} \text{capacity(y)} \geq \text{min\_capacity(y)} \end{aligned}\]

source

Minimum flow constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::MinFlowConstraint, e::Edge, model::Model)

Add a min flow constraint to the edge e. The functional form of the constraint is:

\[\begin{aligned} \text{flow(e, t)} \geq \text{min\_flow\_fraction(e)} \times \text{capacity(e)} \end{aligned}\]

for each time t in time_interval(e) for the edge e.

Note

This constraint is available only for unidirectional edges.

source
MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::MinFlowConstraint, e::EdgeWithUC, model::Model)

Add a min flow constraint to the edge e with unit commitment. The functional form of the constraint is:

\[\begin{aligned} \text{flow(e, t)} \geq \text{min\_flow\_fraction(e)} \times \text{capacity\_size(e)} \times \text{ucommit(e, t)} \end{aligned}\]

for each time t in time_interval(e) for the edge e.

Note

This constraint is available only for unidirectional edges.

source

Minimum storage level constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::MinStorageLevelConstraint, g::AbstractStorage, model::Model)

Add a min storage level constraint to the storage g. The functional form of the constraint is:

\[\begin{aligned} \text{storage\_level(g, t)} \geq \text{min\_storage\_level(g)} \times \text{capacity(g)} \end{aligned}\]

for each time t in time_interval(g) for the storage g.

source

Minimum storage outflow constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::MinStorageOutflowConstraint, g::AbstractStorage, model::Model)

Add a min storage outflow constraint to the storage g part of a HydroRes asset. The functional form of the constraint is:

\[\begin{aligned} \text{flow(spillage\_edge, t)} + \text{flow(discharge\_edge, t)} \geq \text{min\_outflow\_fraction(g)} \times \text{capacity(discharge\_edge)} \end{aligned}\]

for each time t in time_interval(g) for the storage g.

Only applies to HydroRes assets

This constraint only applies to HydroRes assets. It returns a warning if the storage g does not have a spillage edge. If the discharge edge is the only outflow, you should apply MinFlowConstraint to the discharge edge.

source

Minimum up and down time constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::MinUpTimeConstraint, e::EdgeWithUC, model::Model)

Add a min up time constraint to the edge e with unit commitment. The functional form of the constraint is:

\[\begin{aligned} \text{ucommit(e, t)} \geq \sum_{h=0}^{\text{min\_up\_time(e)}-1} \text{ustart(e, t-h)} \end{aligned}\]

for each time t in time_interval(e) for the edge e. The function timestepbefore is used to perform the time wrapping within the subperiods and get the correct time step before t.

Min up time duration

This constraint will throw an error if the minimum up time is longer than the length of one subperiod.

source
MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::MinDownTimeConstraint, e::EdgeWithUC, model::Model)

Add a min down time constraint to the edge e with unit commitment. The functional form of the constraint is:

\[\begin{aligned} \frac{\text{capacity(e)}}{\text{capacity\_size(e)}} - \text{ucommit(e, t)} \geq \sum_{h=0}^{\text{min\_down\_time(e)}-1} \text{ushut(e, t-h)} \end{aligned}\]

for each time t in time_interval(e) for the edge e. The function timestepbefore is used to perform the time wrapping within the subperiods and get the correct time step before t.

Min down time duration

This constraint will throw an error if the minimum down time is longer than the length of one subperiod.

source

Must-run constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::MustRunConstraint, e::Edge, model::Model)

Add a must run constraint to the edge e. The functional form of the constraint is:

\[\begin{aligned} \text{flow(e, t)} = \text{availability(e, t)} \times \text{capacity(e)} \end{aligned}\]

for each time t in time_interval(e) for the edge e.

Must run constraint

This constraint is available only for unidirectional edges.

source

Ramping limits constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::RampingLimitConstraint, e::Edge, model::Model)

Add a ramping limit constraint to the edge e. The functional form of the ramping up limit constraint is:

\[\begin{aligned} \text{flow(e, t)} - \text{flow(e, t-1)} + \text{regulation\_term(e, t)} + \text{reserves\_term(e, t)} - \text{ramp\_up\_fraction(e)} \times \text{capacity(e)} \leq 0 \end{aligned}\]

On the other hand, the ramping down limit constraint is:

\[\begin{aligned} \text{flow(e, t-1)} - \text{flow(e, t)} + \text{regulation\_term(e, t)} + \text{reserves\_term(e, t)} - \text{ramp\_down\_fraction(e)} \times \text{capacity(e)} \leq 0 \end{aligned}\]

for each time t in time_interval(e) for the edge e. The function timestepbefore is used to perform the time wrapping within the subperiods and get the correct time step before t.

source
MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::RampingLimitConstraint, e::EdgeWithUC, model::Model)

Add a ramping limit constraint to the edge e with unit commitment. The functional form of the ramping up limit constraint is:

\[\begin{aligned} \text{flow(e, t)} - \text{flow(e, t-1)} + \text{regulation\_term(e, t)} + \text{reserves\_term(e, t)} - \text{ramp\_up\_fraction(e)} \times \text{capacity\_size(e)} \times (\text{ucommit(e, t)} - \text{ustart(e, t)}) + \text{min(availability(e, t), max(min\_flow\_fraction(e), ramp\_up\_fraction(e)))} \times \text{capacity\_size(e)} \times \text{ustart(e, t)} - \text{min\_flow\_fraction(e)} \times \text{capacity\_size(e)} \times \text{ushut(e, t)} \leq 0 \end{aligned}\]

On the other hand, the ramping down limit constraint is:

\[\begin{aligned} \text{flow(e, t-1)} - \text{flow(e, t)} + \text{regulation\_term(e, t)} + \text{reserves\_term(e, t)} - \text{ramp\_down\_fraction(e)} \times \text{capacity\_size(e)} \times (\text{ucommit(e, t)} - \text{ustart(e, t)}) - \text{min\_flow\_fraction(e)} \times \text{capacity\_size(e)} \times \text{ustart(e, t)} + \text{min(availability(e, t), max(min\_flow\_fraction(e), ramp\_down\_fraction(e)))} \times \text{capacity\_size(e)} \times \text{ushut(e, t)} \leq 0 \end{aligned}\]

for each time t in time_interval(e) for the edge e. The function timestepbefore is used to perform the time wrapping within the subperiods and get the correct time step before t.

source

Storage capacity constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::StorageCapacityConstraint, g::AbstractStorage, model::Model)

Add a storage capacity constraint to the storage g. The functional form of the constraint is:

\[\begin{aligned} \text{storage\_level(g, t)} \leq \text{capacity(g)} \end{aligned}\]

for each time t in time_interval(g) for the storage g.

source

Storage discharge limit constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::StorageDischargeLimitConstraint, e::Edge, model::Model)

Add a storage discharge limit constraint to the edge e if the start vertex of the edge is a storage. The functional form of the constraint is:

\[\begin{aligned} \frac{\text{flow(e, t)}}{\text{efficiency(e)}} \leq \text{storage\_level(start\_vertex(e), timestepbefore(t, 1, subperiods(e)))} \end{aligned}\]

for each time t in time_interval(e) for the edge e. The function timestepbefore is used to perform the time wrapping within the subperiods and get the correct time step before t.

Storage discharge limit constraint

This constraint is only applied to edges with a start vertex that is a storage.

source

Storage symmetric capacity constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(
    ct::StorageSymmetricCapacityConstraint,
    g::AbstractStorage,
    model::Model,
)

Add a storage symmetric capacity constraint to the storage g. The functional form of the constraint is:

\[\begin{aligned} \text{flow(e\_discharge, t)} + \text{flow(e\_charge, t)} \leq \text{capacity(e\_discharge)} \end{aligned}\]

source

Storage charge discharge ratio constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(
    ct::StorageChargeDischargeRatioConstraint,
    g::AbstractStorage,
    model::Model,
)

Add a storage charge discharge ratio constraint to the storage g. The functional form of the constraint is:

\[\begin{aligned} \text{charge\_discharge\_ratio(g)} \times \text{capacity(g.discharge\_edge)} = \text{capacity(g.charge\_edge)} \end{aligned}\]

source

Storage max duration constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::StorageMaxDurationConstraint, g::AbstractStorage, model::Model)

Add a storage max duration constraint to the storage g. The functional form of the constraint is:

\[\begin{aligned} \text{capacity(g)} \leq \text{max\_duration(g)} \times \text{capacity(discharge\_edge(g))} \end{aligned}\]

Storage max duration constraint

This constraint is only applied if the maximum duration is greater than 0.

source

Storage min duration constraint

MacroEnergy.add_model_constraint!Method
add_model_constraint!(ct::StorageMinDurationConstraint, g::AbstractStorage, model::Model)

Add a storage min duration constraint to the storage g. The functional form of the constraint is:

\[\begin{aligned} \text{capacity(g)} \geq \text{min\_duration(g)} \times \text{capacity(discharge\_edge(g))} \end{aligned}\]

Storage min duration constraint

This constraint is only applied if the minimum duration is greater than 0.

source